package com.example.camerastudy.component;

import ohos.agp.components.AttrSet;
import ohos.agp.components.StackLayout;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.Surface;
import ohos.agp.graphics.SurfaceOps;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.camera.CameraKit;
import ohos.media.camera.device.*;
import ohos.media.image.Image;
import ohos.media.image.ImageReceiver;
import ohos.media.image.common.ImageFormat;
import ohos.media.image.common.Size;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_PARENT;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PICTURE;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PREVIEW;

public class CameraView extends StackLayout {

    private static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.DEBUG, 3, "CameraView");

    private static final String CAMERA_RUNNER_NAME = "cameraRunner";

    private SurfaceProvider surfaceProvider;

    private Surface previewSurface;

    private Camera mCamera;

    private Size properSize;

    private ImageReceiver imageReceiver;

    public interface ImageDataCallback {
        void onImageArrive(byte[] bytes);
    }

    private ImageDataCallback imageDataCallback;

    public void setImageDataCallback(ImageDataCallback imageDataCallback) {
        this.imageDataCallback = imageDataCallback;
    }

    private ImageReceiver.IImageArrivalListener imageArrivalListener = imageReceiver -> {
        if (imageDataCallback != null) {
            Image image = imageReceiver.readNextImage();
            Image.Component component = image.getComponent(ImageFormat.ComponentType.JPEG);
            byte[] bytes = new byte[component.remaining()];
            component.read(bytes);
            imageDataCallback.onImageArrive(bytes);
            image.release();
        }
    };

    public CameraView(Context context) {
        super(context);
        initView();
    }

    public CameraView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        initView();
    }

    public CameraView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initView();
    }

    private void initView() {
        surfaceProvider = new SurfaceProvider(getContext());
        addComponent(surfaceProvider, MATCH_PARENT, MATCH_PARENT);
    }

    private Size findBestPreviewSize(CameraKit cameraKit, String cameraId) {
        Size properSize;
        List<Size> supportedSizeList = cameraKit.getCameraAbility(cameraId).getSupportedSizes(ImageFormat.YUV420_888);
        float width = getWidth();
        float height = getHeight();
        float ratio = width / height * 1f;
        List<Size> sameScaleSizeList = new ArrayList<>();
        for (Size size : supportedSizeList) {
            if (Math.abs((size.width * 1f) / (size.height * 1f) - ratio) < 0.05 && size.width > width) {
                sameScaleSizeList.add(size);
            }
        }
        if (sameScaleSizeList.size() > 0) {
            sameScaleSizeList.sort(Comparator.comparingInt(t0 -> t0.width));
            properSize = sameScaleSizeList.get(0);
        } else {
            properSize = supportedSizeList.get(0);
        }
        return properSize;
    }

    private CameraStateCallback mCameraStateCallback = new CameraStateCallback() {

        @Override
        public void onCreated(Camera camera) {
            super.onCreated(camera);
            debug("camera create success");
            mCamera = camera;
            CameraConfig.Builder cameraConfigBuilder = mCamera.getCameraConfigBuilder();
            cameraConfigBuilder.setFrameStateCallback(mFrameStateCallback, new EventHandler(EventRunner.create("frame handler")));
            cameraConfigBuilder.addSurface(previewSurface);
            cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
            mCamera.configure(cameraConfigBuilder.build());
        }

        @Override
        public void onCreateFailed(String cameraId, int errorCode) {
            super.onCreateFailed(cameraId, errorCode);
            debug("camera create failed:" + errorCode);
        }

        @Override
        public void onConfigured(Camera camera) {
            super.onConfigured(camera);
            debug("camera configure success");
            FrameConfig.Builder frameConfigBuilder = mCamera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            frameConfigBuilder.addSurface(previewSurface);
            try {
                mCamera.triggerLoopingCapture(frameConfigBuilder.build());
            } catch (IllegalArgumentException | IllegalStateException e) {
                e.printStackTrace();
                debug("configure error:" + e.getMessage());
            }

        }

        @Override
        public void onConfigureFailed(Camera camera, int errorCode) {
            super.onConfigureFailed(camera, errorCode);
            debug("camera configure fail");
        }

        @Override
        public void onPartialConfigured(Camera camera) {
            super.onPartialConfigured(camera);
            debug("camera partial configured");
        }

        @Override
        public void onCaptureIdle(Camera camera) {
            super.onCaptureIdle(camera);
            debug("camera capture idle");
        }

        @Override
        public void onFatalError(Camera camera, int errorCode) {
            super.onFatalError(camera, errorCode);
            debug("camera fatal error:" + errorCode);
        }

        @Override
        public void onCaptureRun(Camera camera) {
            super.onCaptureRun(camera);
            debug("camera capture run");
        }

        @Override
        public void onReleased(Camera camera) {
            super.onReleased(camera);
            debug("camera released");
        }
    };

    private void debug(String message) {
        HiLog.error(LOG_LABEL, message);
    }

    private FrameStateCallback mFrameStateCallback = new FrameStateCallback() {
        @Override
        public void onFrameStarted(Camera camera, FrameConfig frameConfig, long frameNumber, long timestamp) {
            super.onFrameStarted(camera, frameConfig, frameNumber, timestamp);
        }

        @Override
        public void onFrameProgressed(Camera camera, FrameConfig frameConfig, FrameResult frameResult) {
            super.onFrameProgressed(camera, frameConfig, frameResult);
        }

        @Override
        public void onFrameFinished(Camera camera, FrameConfig frameConfig, FrameResult frameResult) {
            super.onFrameFinished(camera, frameConfig, frameResult);
        }

        @Override
        public void onFrameError(Camera camera, FrameConfig frameConfig, int errorCode, FrameResult frameResult) {
            super.onFrameError(camera, frameConfig, errorCode, frameResult);
        }

        @Override
        public void onCaptureTriggerStarted(Camera camera, int captureTriggerId, long firstFrameNumber) {
            super.onCaptureTriggerStarted(camera, captureTriggerId, firstFrameNumber);
        }

        @Override
        public void onCaptureTriggerFinished(Camera camera, int captureTriggerId, long lastFrameNumber) {
            super.onCaptureTriggerFinished(camera, captureTriggerId, lastFrameNumber);
        }

        @Override
        public void onCaptureTriggerInterrupted(Camera camera, int captureTriggerId) {
            super.onCaptureTriggerInterrupted(camera, captureTriggerId);
        }
    };

    public void startPreview() {
        surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceOps.Callback() {
            @Override
            public void surfaceCreated(SurfaceOps surfaceOps) {
                surfaceProvider.getSurfaceOps().get().setFixedSize(getHeight(), getWidth());
                previewSurface = surfaceOps.getSurface();
                createCamera();
            }

            @Override
            public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {

            }

            @Override
            public void surfaceDestroyed(SurfaceOps surfaceOps) {

            }
        });
    }

    public void takePicture() {
        // 获取拍照配置模板
        FrameConfig.Builder framePictureConfigBuilder = mCamera.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
        // 配置拍照 Surface
        framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
        try {
            // 启动单帧捕获(拍照)
            int id = mCamera.triggerSingleCapture(framePictureConfigBuilder.build());
        } catch (IllegalArgumentException e) {
            debug("Argument Exception:" + e.getMessage());
        } catch (IllegalStateException e) {
            debug("State Exception:" + e.getMessage());
        }
    }

    public void takeMultiPicture() {
        // 获取拍照配置模板
        FrameConfig.Builder framePictureConfigBuilder = mCamera.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
        // 配置拍照 Surface
        framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
        try {
            // 启动多帧捕获(连拍)
            FrameConfig config1 = framePictureConfigBuilder.build();
            FrameConfig config2 = framePictureConfigBuilder.build();
            ArrayList configList = new ArrayList();
            configList.add(config1);
            configList.add(config2);
            int id = mCamera.triggerMultiCapture(configList);
        } catch (IllegalArgumentException e) {
            debug("Argument Exception:" + e.getMessage());
        } catch (IllegalStateException e) {
            debug("State Exception:" + e.getMessage());
        }
    }

    private void createCamera() {
        CameraKit cameraKit = CameraKit.getInstance(getContext());
        if (cameraKit == null) {
            debug("cameraKit is null");
            return;
        }

        String[] cameraIds = cameraKit.getCameraIds();
        if (cameraIds.length == 0) {
            debug("No available camera");
            return;
        }

        String cameraId = cameraIds[0];

        properSize = findBestPreviewSize(cameraKit, cameraId);

        // 创建 ImageReceiver 对象，注意 create 函数中宽度要大于高度； 5 为最大支持的图像数，请根据实际设置。
        imageReceiver = ImageReceiver.create(Math.max(properSize.width, properSize.height),
                Math.min(properSize.width, properSize.height), ImageFormat.JPEG, 1);
        imageReceiver.setImageArrivalListener(imageArrivalListener);

        cameraKit.createCamera(cameraId, mCameraStateCallback, new EventHandler(EventRunner.create(CAMERA_RUNNER_NAME)));
    }


    public void onStop() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
        surfaceProvider.removeFromWindow();
    }
}
